package com.github.shuaidd.service.editor;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.shuaidd.service.mapper.ModelEntityService;
import com.github.shuaidd.service.mapper.ModelHistoryEntityService;
import lombok.AllArgsConstructor;
import org.activiti.app.domain.editor.*;
import org.activiti.app.security.SecurityUtils;
import org.activiti.app.service.api.AppDefinitionService;
import org.activiti.app.service.api.AppDefinitionServiceRepresentation;
import org.activiti.app.service.exception.InternalServerErrorException;
import org.activiti.editor.language.json.converter.util.CollectionUtils;
import org.activiti.engine.identity.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
@Transactional
@AllArgsConstructor(onConstructor_ = {@Autowired})
public class LwfAppDefinitionService implements AppDefinitionService {

  private final Logger logger = LoggerFactory.getLogger(LwfAppDefinitionService.class);
  private final ModelEntityService modelRepository;
  private final ModelHistoryEntityService modelHistoryRepository;
  private final ObjectMapper objectMapper;

  @Override
  public List<AppDefinitionServiceRepresentation> getAppDefinitions() {
    Map<String, AbstractModel> modelMap = new HashMap<String, AbstractModel>();
    List<AppDefinitionServiceRepresentation> resultList = new ArrayList<AppDefinitionServiceRepresentation>();

    User user = SecurityUtils.getCurrentUserObject();
    List<Model> createdByModels = modelRepository.findModelsCreatedBy(user.getId(), AbstractModel.MODEL_TYPE_APP, null,"name asc");
    for (Model model : createdByModels) {
      modelMap.put(model.getId(), model);
    }

    for (AbstractModel model : modelMap.values()) {
      resultList.add(createAppDefinition(model));
    }

    return resultList;
  }

  /**
   * Gathers all 'deployable' app definitions for the current user.
   * 
   * To find these: - All historical app models are fetched. Only the highest version of each app model is retained. - All historical app models shared with the groups the current user is part of are
   * fetched. Only the highest version of each app model is retained.
   */
  @Override
  public List<AppDefinitionServiceRepresentation> getDeployableAppDefinitions(User user) {
    Map<String, ModelHistory> modelMap = new HashMap<>();
    List<AppDefinitionServiceRepresentation> resultList = new ArrayList<AppDefinitionServiceRepresentation>();

    List<ModelHistory> createdByModels = modelHistoryRepository.findByCreatedByAndModelTypeAndRemovalDateIsNull(user.getId(), AbstractModel.MODEL_TYPE_APP);
    for (ModelHistory modelHistory : createdByModels) {
      if (modelMap.containsKey(modelHistory.getModelId())) {
        if (modelHistory.getVersion() > modelMap.get(modelHistory.getModelId()).getVersion()) {
          modelMap.put(modelHistory.getModelId(), modelHistory);
        }
      } else {
        modelMap.put(modelHistory.getModelId(), modelHistory);
      }
    }

    for (ModelHistory model : modelMap.values()) {
      Model latestModel = modelRepository.selectByPrimaryKey(model.getModelId());
      if (latestModel != null) {
        resultList.add(createAppDefinition(model));
      }
    }

    return resultList;
  }

  protected AppDefinitionServiceRepresentation createAppDefinition(AbstractModel model) {
    AppDefinitionServiceRepresentation resultInfo = new AppDefinitionServiceRepresentation();
    if (model instanceof ModelHistory) {
      resultInfo.setId(((ModelHistory) model).getModelId());
    } else {
      resultInfo.setId(model.getId());
    }
    resultInfo.setName(model.getName());
    resultInfo.setDescription(model.getDescription());
    resultInfo.setVersion(model.getVersion());
    resultInfo.setDefinition(model.getModelEditorJson());

    AppDefinition appDefinition = null;
    try {
      appDefinition = objectMapper.readValue(model.getModelEditorJson(), AppDefinition.class);
    } catch (Exception e) {
      logger.error("Error deserializing app " + model.getId(), e);
      throw new InternalServerErrorException("Could not deserialize app definition");
    }

    if (appDefinition != null) {
      resultInfo.setTheme(appDefinition.getTheme());
      resultInfo.setIcon(appDefinition.getIcon());
      List<AppModelDefinition> models = appDefinition.getModels();
      if (CollectionUtils.isNotEmpty(models)) {
        List<String> modelIds = new ArrayList<String>();
        for (AppModelDefinition appModelDef : models) {
          modelIds.add(appModelDef.getId());
        }
        resultInfo.setModels(modelIds);
      }
    }
    return resultInfo;
  }
  
  
  @Override
  public String getDefinitionIdForModelAndUser(String modelId, User user) {
    return this.modelRepository.appDefinitionIdByModelAndUser(modelId, user.getId());
  }

}
